interface ShaderSource {
    vertex: string;
    fragment: string;
}

class Program {
    private _program: WebGLProgram;
    private _gl: WebGLRenderingContext;
    private _attributes: Map<string, number> = new Map();
    private _uniforms: Map<string, WebGLUniformLocation> = new Map();

    constructor(gl: WebGLRenderingContext, shaderSource: ShaderSource) {
        this._gl = gl;
        const program = gl.createProgram();
        if (!program) {
            throw new Error('gl: createProgram failed');
        }
        this._program = program;

        const { vertex, fragment } = shaderSource;
        this._initShader(this._gl.VERTEX_SHADER, vertex);
        this._initShader(this._gl.FRAGMENT_SHADER, fragment);

        this._gl.linkProgram(this._program);
    }

    private _initShader(type: number, source: string) {
        const shader = this._gl.createShader(type);
        if (!shader) {
            console.error('Shader: createShader failed');
            return;
        }
        this._gl.shaderSource(shader, source);
        this._gl.compileShader(shader);
        this._gl.attachShader(this._program, shader);
    }

    getAttribLocation(locationName: string) {
        if (this._attributes.has(locationName)) {
            return <number>this._attributes.get(locationName);
        }
        const locationValue = this._gl.getAttribLocation(this._program, locationName);
        this._attributes.set(locationName, locationValue);
        return locationValue;
    }

    getUniformLocation(locationName: string) {
        if (this._uniforms.has(locationName)) {
            return <WebGLUniformLocation>this._uniforms.get(locationName);
        }
        const locationValue = this._gl.getUniformLocation(this._program, locationName);
        locationValue && this._uniforms.set(locationName, locationValue);
        return locationValue;
    }

    getProgram() {
        return this._program;
    }
}

export default Program;
